package com.polito.mad.bikeponyapp.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.net.Uri;
import android.net.Uri.Builder;
import android.os.AsyncTask;

import com.polito.mad.bikeponyapp.R;
import com.polito.mad.bikeponyapp.persistence.DeliveriesRepository;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem.DeliveryItemType;
import com.polito.mad.bikeponyapp.persistence.DeliveryItemDAO;
import com.polito.mad.bikeponyapp.persistence.DeliveryItemState;

public abstract class ServerUpdateAsyncTask extends AsyncTask<ServerUpdateDataContainer, Void, Boolean>{

	private static final String GOOGLE_MAPS_GEOCODING_URL = "http://maps.googleapis.com/maps/api/geocode/json";
	private DeliveriesRepository deliveryItemRepository;
	protected boolean hasUpdateDeliveries;
	protected boolean hasNewDeliveries;
	protected boolean hasToDeleteDeliveries;
	protected String errorMessage;
	protected ResultMessage resultMessage;
	private List<DeliveryItem> newDeliveries;
	private List<DeliveryItem> modifiedDeliveries;
	private List<DeliveryItem>toDeleteDeliveries;
	private Context context;
	
	@Override
	protected void onPreExecute() {
		// TODO Auto-generated method stub
		super.onPreExecute();
	}
	public ServerUpdateAsyncTask(DeliveriesRepository deliveryRepository,Context context) {
		this.deliveryItemRepository= deliveryRepository;
		this.context=context;
	}
	
	@Override
	protected Boolean doInBackground(ServerUpdateDataContainer... params) {
		
		if(params==null || params.length!=1)
			return false;
		
		ServerUpdateDataContainer dataContainer = params[0];
		modifiedDeliveries = dataContainer.getDeliveryItems();
		hasUpdateDeliveries=(modifiedDeliveries!=null && !modifiedDeliveries.isEmpty());
		
		try {
			URL url= buildeServerUpdateURL(dataContainer);
			HttpURLConnection connection = (HttpURLConnection)url.openConnection();
			InputStream is = connection.getInputStream();
			if(is!=null)
			{
				BufferedReader br = new BufferedReader(new InputStreamReader(is));
				String line = null;
				StringBuilder sb = new StringBuilder();
				while((line = br.readLine())!=null)
					sb.append(line);
				
				is.close();
				connection.disconnect();
				JSONObject result = new JSONObject(sb.toString());
				sb=null;
				line=null;
				resultMessage = new ResultMessage(result);
				newDeliveries = validateResponse(resultMessage);
				
				hasNewDeliveries = (newDeliveries!=null && !newDeliveries.isEmpty());
				
			}
			return true;
			
		} catch (MalformedURLException e) {
			e.printStackTrace();
			errorMessage="Server connection failed";
		} catch (IOException e) {
			errorMessage = "Network unreachable";
		} catch (JSONException e) {
			e.printStackTrace();
		}catch(Exception e)
		{
			if(errorMessage==null || errorMessage.length()==0)
				errorMessage=e.getLocalizedMessage();
			e.printStackTrace();
		}
		
		return false;
		
	}
	private URL buildeServerUpdateURL(ServerUpdateDataContainer dataContainer) throws MalformedURLException
	{
		StringBuilder b = new StringBuilder();
		b.append(context.getString(R.string.server_com_protocol))
		.append("://")
		.append(context.getString(R.string.server_host))
		.append(":")
		.append(context.getString(R.string.server_port))
		.append(context.getString(R.string.server_services_context_path))
		.append(context.getString(R.string.server_services_agent_path))
		.append(context.getString(R.string.server_services_agent_update_position));
		Builder uriBuilder = new Uri.Builder();
		uriBuilder.encodedPath(b.toString());
		uriBuilder.appendQueryParameter("latitude", dataContainer.getLatitude().toString());
		uriBuilder.appendQueryParameter("longitude", dataContainer.getLongitude().toString());
		uriBuilder.appendQueryParameter("agentId", dataContainer.getAgentId());
		
		List<DeliveryItem> modifiedItems = dataContainer.getDeliveryItems();
		StringBuilder destinationsBuilder = new StringBuilder("[");
		if(modifiedItems!=null)
		{
			for(int i=0;i<modifiedItems.size();i++)
        	{
				destinationsBuilder.append(modifiedItems.get(i).getDeliveryId()+"-"+(modifiedItems.get(i).getDeliveryItemType()==DeliveryItemType.PICKUP)).append(",");
        	}
		}
		destinationsBuilder.append("]");
		uriBuilder.appendQueryParameter("destinations", destinationsBuilder.toString());
		return new URL(uriBuilder.build().toString());
		
	}
	
	private List<DeliveryItem> validateResponse(ResultMessage resultMessage) throws Exception
	{
		List<DeliveryItem> newDeliveries = new ArrayList<DeliveryItem>();
		
		if(resultMessage!=null && resultMessage.getStatusCode()==0)
		{
			JSONObject data = resultMessage.getData();
			JSONArray deliveries = data.getJSONArray("deliveries");
			int lastCounter=-1;
			/*iterate over received Deliveries*/
			StringBuffer sb = new StringBuffer();
			String prefix="";
			for (int i =0;i<deliveries.length();i++)
			{
				JSONObject delivery = deliveries.getJSONObject(i);
				long deliveryId = delivery.getLong("delivery-id");
				sb.append(prefix+deliveryId);
				prefix=",";
				
				List<DeliveryItem> deliveryItems =  deliveryItemRepository.findByQuery(deliveryItemRepository.getColumnNameByIndex(DeliveryItemDAO.DELIVERY_ID_COLUMN_INDEX)+" = "+deliveryId);
				DeliveryItem topickup=null;
				DeliveryItem todelivery=null;
				
				if(deliveryItems.isEmpty())
				{
					/*new Delivery has been found*/
					DeliveryItemState itemState = DeliveryItemState.fromState(delivery.getInt("state"));
					
					if(itemState== DeliveryItemState.DELIVERED)
						continue;
					if(lastCounter==-1)
					{
						lastCounter = deliveryItemRepository.count();
						if(lastCounter==-1)
							lastCounter=0;
					}
					// a new item arrived
					if(itemState==DeliveryItemState.WAITING_FOR_PICKUP)
					{
						topickup = new DeliveryItem();
						topickup.setAddress(delivery.getString("sender-address"));
						topickup.setOrder(lastCounter);
						topickup.setDeliveryItemType(DeliveryItemType.PICKUP);
						topickup.setAdditionalInfo(delivery.optString("sender-additional-info"));
						topickup.setEstimatedTime(delivery.getLong("pickup-estimated-time"));
						topickup.setDeliveryId(deliveryId);
						newDeliveries.add(topickup);
						lastCounter++;
					}
					todelivery = new DeliveryItem();
					todelivery.setAddress(delivery.getString("recipient-address"));
					todelivery.setOrder(lastCounter);
					todelivery.setDeliveryItemType(DeliveryItemType.DELIVERY);
					todelivery.setAdditionalInfo(delivery.optString("recipient-additional-info"));
					todelivery.setEstimatedTime(delivery.getLong("delivery-estimated-time"));
					todelivery.setDeliveryId(deliveryId);
					lastCounter++;
					newDeliveries.add(todelivery);
				}
				
				/*manage the update destination routing : we must keep the order chosen by the user */	
				
			}
			toDeleteDeliveries = deliveryItemRepository.findByQuery(deliveryItemRepository.getColumnNameByIndex(DeliveryItemDAO.DELIVERY_ID_COLUMN_INDEX)+" NOT IN ("+sb.toString()+")");
			if(!toDeleteDeliveries.isEmpty())
				hasToDeleteDeliveries=true;
			
			//start geocoding
			
			updateGeocodingInformation(newDeliveries);
				
		}else 
		{
			errorMessage = resultMessage!=null?resultMessage.getMessage():"Communication error";
			
			// manager the error
			throw new JSONException(errorMessage);
		}
		return newDeliveries;
				
	}

	private void updateGeocodingInformation(List<DeliveryItem> newDeliveries) throws IOException, JSONException 
	{
		
		for(DeliveryItem item:newDeliveries)
		{
			String address = item.getAddress();
			Builder builder =new  Uri.Builder();
			builder.encodedPath(GOOGLE_MAPS_GEOCODING_URL);
			builder.appendQueryParameter("address", address.replaceAll("\\.", ","));
			builder.appendQueryParameter("sensor","false");
			URL url= new URL(builder.build().toString());
			builder.clearQuery();
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			InputStream is = connection.getInputStream();
			if(is!=null)
			{
				try
				{
					BufferedReader br = new BufferedReader(new InputStreamReader(is));
					String line = null;
					StringBuilder sb = new StringBuilder();
					while((line = br.readLine())!=null)
						sb.append(line);
					
					JSONObject jsonResult = new JSONObject(sb.toString());
					sb =null;
					line=null;
					if(!jsonResult.getString("status").equalsIgnoreCase("OK"))
					{
						errorMessage= "invalid geocoding response status";
						throw new JSONException(errorMessage);
					}
					JSONArray results = jsonResult.getJSONArray("results");
					if(results.length()==0)
					{
						errorMessage= "no corresponding location found for "+address;
						throw new JSONException(errorMessage);
					}
						
					JSONObject location = results.getJSONObject(0).getJSONObject("geometry").getJSONObject("location");
					double latitude = location.getDouble("lat");
					double longitude = location.getDouble("lng");
					location= null;
					results=null;
					jsonResult=null;
					
					item.setLatitutde(latitude);
					item.setLongitude(longitude);
					
				}finally
				{
					is.close();
					connection.disconnect();
				}
			}else
			{
				errorMessage="response data not available";
				throw new IOException(errorMessage);
			}
				
		}
		
	}
	
	@Override
	protected final void onPostExecute(Boolean result) {
		super.onPostExecute(result);
		try
		{
			
			if(hasToDeleteDeliveries)
				deleteDeliveries(toDeleteDeliveries);
			if(hasUpdateDeliveries && result)
				updateDeliveries(modifiedDeliveries);
			if(hasNewDeliveries)
				addDeliveries(newDeliveries);
			
			
		}catch(Exception e)
		{
			e.printStackTrace();
			result=false;
		}
		this.onAfterAll(result);
	}
	
	
	protected abstract void onAfterAll(Boolean result);
	
	protected abstract void updateDeliveries(List<DeliveryItem> deliveries);
	
	protected abstract void addDeliveries(List<DeliveryItem> deliveries);
	
	protected abstract void deleteDeliveries(List<DeliveryItem> deliveries);
	

}
